using System;
using System.Collections;
using System.IO;
using System.Globalization;
using System.Net;
using System.Web.Services.Protocols;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Crm;
using Microsoft.Crm.Application.Outlook.OfflineSync;
using PlatformProxy = Microsoft.Crm.Sdk.Proxy;
using SyncAction = Microsoft.Crm.Sdk.Proxy.SyncAction;
using Microsoft.Win32;

namespace OfflineSyncTestDriver
{ 
	public enum OfflineAction
	{
	    GoOffline = 1,
		SetOffline = 2
	}

	public class PerfSync
    {
        SyncDataRecorder sdr = null;

        public PerfSync(SyncDataRecorder sdr)
	    {
            this.sdr = sdr;
	    }

	    #region Properties
        private const int notDefined = -1;

	    private String clientKeyPath = null;
	    private RegistryKey regKey = null;
        private String serverUrl = null;
	    private String subscriptionId = null;
        private bool newSubscription = false;
        private int syncRowsBatchSize = notDefined;
        private int syncMaxRetryCount = notDefined;
        private int syncMinBatchTimeMilliSec = notDefined;
        private int syncMaxBatchTimeMilliSec = notDefined;
        private int syncIncreaseRate = notDefined;
        private int syncDecreaseRate = notDefined;
	    private PlatformProxy.CrmService service = null;

	    private String userName = null;
	    private String userDomain = null;
	    private String machineName = null;
	    private System.Net.ICredentials networkCredential = null;
        private Guid organizationId;

        private Hashtable m_entityData;
        private long totalDataSizeInBytes = 0;
        private int totalSystemRows = 0;
        int totalSystemRowsDownloaded = 0;
        int totalDownloaded = 0;


	    // Need this public property to don't dependent on RegControl in unit test, perf driver, etc.
	    public String ClientKeyPath
	    {
		    get
		    {
			    if (clientKeyPath == null)
				    clientKeyPath = Microsoft.Crm.RegControl.GetCrmTargetKeyPath();

			    return clientKeyPath;
		    }

		    set { clientKeyPath = value; }
        }

        #region Get/Set properties
	    public String UserName
	    {
		    get
		    {
			    if (userName == null)
				    userName = Environment.UserName;

			    return userName;
		    }

		    set { userName = value; }
	    }

	    // Need this public property for CreateSubscription (OfflineSync Stress test) so that we do not use the Environemnt variable for machineName
	    public String MachineName
	    {
		    get
		    {
			    if (machineName == null)
				    machineName = Environment.MachineName;

			    return machineName;
		    }

		    set { machineName = value; }
	    }

	    // Need this public property for CreateSubscription (OfflineSync Stress test) so that we do not use the Environemnt variable for Userdomain
	    public String UserDomain
	    {
		    get
		    {
			    if (userDomain == null)
				    userDomain = Environment.UserDomainName;

			    return userDomain;
		    }

		    set { userDomain = value; }
	    }

	    // Need this public property for webservice (OfflineSync Stress test) so that we do not use the default credential
	    public System.Net.ICredentials NetworkCredential
	    {
		    get
		    {
			    if (networkCredential == null)
				    return System.Net.CredentialCache.DefaultCredentials;
			    else
				    return networkCredential;

		    }

		    set { networkCredential = value; }
        }

        public Guid OrganizationId
        {
            get
            {
                if (organizationId == Guid.Empty)
                {
                    organizationId = Microsoft.Crm.Security.User.Current.OrganizationId;
                }

                return organizationId;
            }
            set { organizationId = value; }
        }
        #endregion

        private RegistryKey RegKey
	    {
		    get
		    {
			    if (this.regKey == null)
			    {
                    this.regKey = Registry.CurrentUser.OpenSubKey(ClientKeyPath, true);
				    if (this.regKey == null)
					    throw new CrmException( "Client registry subkey not found in the registry", ErrorCodes.InvalidRegistryKey );
			    }
			    return this.regKey;
		    }
	    }

        private String ServerUrl
        {
            get
            {
                if (this.serverUrl == null)
                {
                    serverUrl = (String)RegKey.GetValue("ServerUrl");
                    if (serverUrl == null)
                        throw new CrmException("ServerUrl was not found in the registry", ErrorCodes.InvalidRegistryKey);
                }
                return this.serverUrl;
            }
        }

	    private String SubscriptionId
	    {
		    get
		    {
			    if (this.subscriptionId == null)
			    {
				    subscriptionId = (String)RegKey.GetValue( "UserReplicationID" );
			    }
			    return this.subscriptionId;
		    }
		    set
		    {
			    subscriptionId = value;
			    RegKey.SetValue( "UserReplicationID", subscriptionId );
		    }
	    }

        public PlatformProxy.CrmService Service
	    {
            get
            {
                return service;
            }
            
            set { service = value; }
	    }

        private void GetPropertyValue(String name, ref int property)
        {
            if (property == notDefined)
            {
                object regValue = RegKey.GetValue(name);
                if (regValue == null)
                    throw new CrmException(String.Format("{0} was not found in the registry", name), ErrorCodes.InvalidRegistryKey);

                property = (int)regValue;

                if (property < 1)
                    throw new CrmException(String.Format("{0} should be >= 1", name), ErrorCodes.InvalidArgument);
            }
        }

        private int SyncIncreaseRate
        {
            get
            {
                GetPropertyValue("OfflineIncreaseRate", ref syncIncreaseRate);
                return this.syncIncreaseRate;
            }
        }

        private int SyncDecreaseRate
        {
            get
            {
                GetPropertyValue("OfflineDecreaseRate", ref syncDecreaseRate);
                return this.syncDecreaseRate;
            }
        }

        private int SyncRowsBatchSize
        {
            get
            {
                GetPropertyValue("OfflineRowsBatchSize", ref syncRowsBatchSize);
                return this.syncRowsBatchSize;
            }
        }

        private int SyncMinBatchTimeMilliSec
        {
            get
            {
                GetPropertyValue("OfflineMinBatchTimeMilliSec", ref syncMinBatchTimeMilliSec);
                return syncMinBatchTimeMilliSec;
            }
        }

        private int SyncMaxBatchTimeMilliSec
        {
            get
            {
                GetPropertyValue("OfflineMaxBatchTimeMilliSec", ref syncMaxBatchTimeMilliSec);

                if (syncMaxBatchTimeMilliSec <= SyncMinBatchTimeMilliSec)
                    throw new CrmException("OfflineMaxBatchTimeMilliSec should be >= OfflineMinBatchTimeMilliSec", ErrorCodes.InvalidArgument);

                return syncMaxBatchTimeMilliSec;
            }
        }

        private int SyncMaxRetryCount
        {
            get
            {
                GetPropertyValue("OfflineMaxRetryCount", ref syncMaxRetryCount);
                return this.syncMaxRetryCount;
            }
        }

        private bool NewSubscription
        {
            get { return this.newSubscription; }
            set { this.newSubscription = value; }
        }

	    #endregion

	    private void CreateSubscription()
	    {
		    try
		    {
			    if (SubscriptionId != null)
				    throw new CrmException( "CreateSubscription should be called when no subscriptionId exists.", ErrorCodes.InvalidOperation );

			    String machineName = MachineName;
			    String userDomain = UserDomain;

			    PlatformProxy.subscription subscription = new PlatformProxy.subscription();
			    subscription.machinename = userDomain + "\\" + machineName;
			    PlatformProxy.CrmNumber type = new PlatformProxy.CrmNumber();
			    type.Value = PlatformProxy.SubscriptionType.Offline;
			    subscription.subscriptiontype = type;

			    PlatformProxy.CreateSubscriptionRequest request = new PlatformProxy.CreateSubscriptionRequest();
			    request.Target = subscription;

			    PlatformProxy.CreateSubscriptionResponse response = (PlatformProxy.CreateSubscriptionResponse)Service.Execute( request );

			    SubscriptionId = response.id.ToString( "B" ).ToUpper( CultureInfo.InvariantCulture );
		    }
		    finally
		    {
		    }
	    }

        public void Sync(ITransferDataProvider transferDataProvider)
	    {
		    try
		    {
                // Step1 : CreateSubscription 
                DateTime dtStart, dtFinish;

			    if (SubscriptionId == null)
                {
		            dtStart = DateTime.Now;
		            CreateSubscription();
		            dtFinish = DateTime.Now;
		            if (sdr != null)
		            {
		            	sdr.CreateSubscription = dtFinish - dtStart;
		            }
                }

			    // Step2 : PrepareSync
		        dtStart = DateTime.Now;
	        	String syncInfo = DoPrepareSync();
		        dtFinish = DateTime.Now;
		        if (sdr != null)
		        {
			        sdr.PrepareSync = dtFinish - dtStart;
			        sdr.SyncInfo = syncInfo;
		        }

                BuildEntityMeta(syncInfo);

                bool schemaChanged = true;
                
                DoInitTransferDataProvider(transferDataProvider, schemaChanged);

				dtStart = DateTime.Now;
                MoveData(syncInfo, transferDataProvider, 100);
		        dtFinish = DateTime.Now;
		        if (sdr != null)
		        {
			        sdr.MoveData = dtFinish - dtStart;
		        }
		    }
		    finally
		    {
		    }

            string strDate = DateTime.Now.ToString("s", CultureInfo.InvariantCulture);
            strDate += "Z";
            RegKey.SetValue("OfflineLastUpdateTime", strDate);
	    }

        private void BuildEntityMeta(string syncInfo)
        {
            m_entityData = new Hashtable();
            XmlDocument doc = SharedUtil.CreateXmlDocument(syncInfo);
            XPathNavigator nav = doc.CreateNavigator();
            XPathNodeIterator iterator = nav.Select("/result/entity");
            int count = iterator.Count;
            if (count == 0)
            {
                return;
            }

            bool isFiltered;
            while (iterator.MoveNext())
            {
                XmlNode node = ((IHasXmlNode)iterator.Current).GetNode();
                XmlAttributeCollection xmlAttributes = node.Attributes;
                String entityName = xmlAttributes["name"].Value;
                isFiltered = IsFilteredEntity(entityName);
                EntityMeta currentEntity = new EntityMeta(entityName, isFiltered);
                m_entityData.Add(entityName, currentEntity);
            }
        }

        private bool IsFilteredEntity(string entityName)
        {
            bool filtered;

            switch (entityName)
            {
                case "Task":
                    filtered = true;
                    break;
                case "Invoice":
                    filtered = true;
                    break;
                case "Contact":
                    filtered = true;
                    break;
                case "ServiceAppointment":
                    filtered = true;
                    break;
                case "Incident":
                    filtered = true;
                    break;
                case "Campaign":
                    filtered = true;
                    break;
                case "CampaignActivity":
                    filtered = true;
                    break;
                case "CampaignResponse":
                    filtered = true;
                    break;
                case "OpportunityClose":
                    filtered = true;
                    break;
                case "Quote":
                    filtered = true;
                    break;
                case "Account":
                    filtered = true;
                    break;
                case "Lead":
                    filtered = true;
                    break;
                case "Email":
                    filtered = true;
                    break;
                case "QuoteClose":
                    filtered = true;
                    break;
                case "OrderClose":
                    filtered = true;
                    break;
                case "SalesOrder":
                    filtered = true;
                    break;
                case "Annotation":
                    filtered = true;
                    break;
                case "SalesLiterature":
                    filtered = true;
                    break;
                case "Product":
                    filtered = true;
                    break;
                case "Opportunity":
                    filtered = true;
                    break;
                case "List":
                    filtered = true;
                    break;
                default:
                    filtered = false;
                    break;
            }
            return filtered;
        }

        private class EntityMeta
        {
            public EntityMeta(string entityName, bool IsRepUserFiltered)
            {
                EntityName = entityName;
                IsReplicationUserFiltered = IsRepUserFiltered;
            }
            public string EntityName;
            public bool IsReplicationUserFiltered;
        }

	    private String DoPrepareSync()
	    {
		    try
		    {
			    PlatformProxy.PrepareSyncSubscriptionRequest request = new PlatformProxy.PrepareSyncSubscriptionRequest();
			    request.SubscriptionId = new Guid( SubscriptionId );

			    PlatformProxy.PrepareSyncSubscriptionResponse response = (PlatformProxy.PrepareSyncSubscriptionResponse)
																	     Service.Execute( request );
			    return response.SyncInfoXml;
		    }
		    finally
		    {
		    }
	    }

        private void DoInitTransferDataProvider(ITransferDataProvider transferDataProvider, bool schemaChanged)
        {
            try
            {
                transferDataProvider.Init(this.SubscriptionId, this.ServerUrl, OrganizationId, schemaChanged);
            }
            finally
            {
            }
        }

        private class SyncInfoNode
        {
            public SyncInfoNode(EntityMeta entity, int insertCount, int deleteCount)
            {
                Entity = entity;
                InsertCount = insertCount;
                DeleteCount = deleteCount;
                TotalCount = insertCount + deleteCount;
            }
            public EntityMeta Entity;
            public int InsertCount;
            public int DeleteCount;
            public int TotalCount;
        }

        private class SyncInfoNodeCompareClass : IComparer
        {
            int IComparer.Compare(Object x, Object y)
            {
                SyncInfoNode nodeX = (SyncInfoNode)x;
                SyncInfoNode nodeY = (SyncInfoNode)y;
                int xValue = nodeX.Entity.IsReplicationUserFiltered ? 1 : 0;
                int yValue = nodeY.Entity.IsReplicationUserFiltered ? 1 : 0;
                return (yValue - xValue);
            }
        }

        protected virtual void MoveData(String syncInfo, ITransferDataProvider transferDataProvider, int progressSteps)
        {
            bool dropCreateIndexes = NewSubscription;
            try
            {
                XmlDocument doc = SharedUtil.CreateXmlDocument(syncInfo);
                XPathNavigator nav = doc.CreateNavigator();
                XPathNodeIterator iterator = nav.Select("/result/entity");
                int count = iterator.Count;
                if (count == 0)
                {
                    return;
                }

                ArrayList syncInfoArray = new ArrayList(count);
                while (iterator.MoveNext())
                {
                    XmlNode node = ((IHasXmlNode)iterator.Current).GetNode();
                    XmlAttributeCollection xmlAttributes = node.Attributes;

                    String entityName = xmlAttributes["name"].Value;
                    EntityMeta entity = (EntityMeta)m_entityData[entityName];

                    int countDelete = ConvertAttributeToInt(xmlAttributes["delete_count"]);
                    int countInsert = ConvertAttributeToInt(xmlAttributes["insert_count"]);

                    SyncInfoNode synInfoNode = new SyncInfoNode(entity, countInsert, countDelete);
                    syncInfoArray.Add(synInfoNode);

                    if (!entity.IsReplicationUserFiltered)
                    {
                        totalSystemRows += countDelete + countInsert;
                    }
                }

                IComparer syncInfoNodeComparer = new SyncInfoNodeCompareClass();
                syncInfoArray.Sort(syncInfoNodeComparer);

                float stepProgress = progressSteps / count;
                int startProgress = 100 - progressSteps;

                bool userFilteredEntity = true;

                foreach (SyncInfoNode syncInfoNode in syncInfoArray)
                {
                    if (!syncInfoNode.Entity.IsReplicationUserFiltered && userFilteredEntity)
                    {
                        userFilteredEntity = false;
                    }

                    ExecuteBatch(transferDataProvider, syncInfoNode.Entity.EntityName, syncInfoNode.DeleteCount, SyncAction.Delete,
                        syncInfoNode.Entity.IsReplicationUserFiltered ? 0 : totalSystemRowsDownloaded,
                        syncInfoNode.Entity.IsReplicationUserFiltered ? syncInfoNode.TotalCount : totalSystemRows);
                    ExecuteBatch(transferDataProvider, syncInfoNode.Entity.EntityName, syncInfoNode.InsertCount, SyncAction.Insert,
                        syncInfoNode.Entity.IsReplicationUserFiltered ? syncInfoNode.DeleteCount : totalSystemRowsDownloaded,
                        syncInfoNode.Entity.IsReplicationUserFiltered ? syncInfoNode.TotalCount : totalSystemRows);
                }
            }
            finally
            {
            }
        }

        private int ConvertAttributeToInt(XmlAttribute attribute)
        {
            if (attribute == null)
                return 0;

            try
            {
                int count = Int32.Parse(attribute.Value);
                return count;
            }
            catch
            {
                throw new CrmArgumentException("Cannot convert attribute to integer.", attribute.Value);
            }
        }

        private void ExecuteBatch(ITransferDataProvider transferDataProvider, String entityName, int countRows, SyncAction action, int totalCompleted, int total)
        {
            if (countRows <= 0)
                return;

            if (!DoBatch(transferDataProvider, entityName, countRows, action, totalCompleted, total))
            {
                throw new CrmException("Error in DoBatch", ErrorCodes.GoOfflineFailedMoveData);
            }
        }

        private bool DoBatch(ITransferDataProvider transferDataProvider, String entityName, int countRows, SyncAction action, int totalCompleted, int total)
        {
            int currentBatchSize = countRows < SyncRowsBatchSize ? countRows : SyncRowsBatchSize;
            int retryCount = 1;

            EntityMeta entity = (EntityMeta)m_entityData[entityName];
            DateTime startTime;

            while (countRows > 0 && retryCount <= SyncMaxRetryCount)
            {
                int batchRows = currentBatchSize == 0 || countRows <= currentBatchSize ? 0 : currentBatchSize;
                int rowsInBatchStep = batchRows == 0 ? countRows : currentBatchSize;

                startTime = DateTime.Now;

                bool succeeded = DoBatchStep(transferDataProvider, entityName, action, batchRows);

                if (!succeeded)
                {
                    currentBatchSize = GetDecreasedBatchSize(currentBatchSize);
                    retryCount++;
                    continue;
                }

                currentBatchSize = GetBatchSize(currentBatchSize, DateTime.Now - startTime);

                CallPostSync(entity, action, batchRows);

                if (countRows > 0)
                {
                    totalCompleted += (batchRows == 0 ? countRows : batchRows);
                }
                countRows -= batchRows == 0 ? countRows : batchRows;

                totalDownloaded += rowsInBatchStep;
                if (!entity.IsReplicationUserFiltered)
                {
                    totalSystemRowsDownloaded += rowsInBatchStep;
                }
            }

            return retryCount <= SyncMaxRetryCount;
        }

        private bool DoBatchStep(ITransferDataProvider transferDataProvider, String entityName, SyncAction action, int batchRows)
        {
            try
            {
                switch (action)
                {
                    case SyncAction.Delete:
                        this.totalDataSizeInBytes += ExecuteDelete(entityName, batchRows);
                        break;
                    case SyncAction.Insert:
                        this.totalDataSizeInBytes += ExecuteInsert(entityName, batchRows);
                        break;
                    default:
                        throw new CrmArgumentException("Action should not be 'All'", "action");
                }
            }
            catch (CrmException e)
            {
                switch (e.ErrorCode)
                {
                    case ErrorCodes.GoOfflineGetBCPFileException:
                    case ErrorCodes.GoOfflineServerFailedGenerateBCPFile:
                    case ErrorCodes.GoOfflineBCPFileSize:
                        return false;
                    default: break;
                }
                throw e;
            }
            catch (Exception e)
            {
                String message = String.Format(CultureInfo.InvariantCulture, "{0} failed for entity '{1}', batchRows={2} with exception {3}",
                    action.ToString(), entityName,
                    batchRows.ToString(CultureInfo.InvariantCulture),
                    e);
                Utility.LogTraceError(message);

                return false;
            }

            return true;
        }

        private int GetBatchSize(int currentSize, TimeSpan time)
        {
            int newSize;

            if (time.TotalMilliseconds > this.SyncMaxBatchTimeMilliSec)
                newSize = GetDecreasedBatchSize(currentSize, time);
            else if (time.TotalMilliseconds < this.SyncMinBatchTimeMilliSec)
                newSize = GetIncreasedBatchSize(currentSize, time);
            else
                newSize = currentSize;

            return newSize;
        }

        private int GetIncreasedBatchSize(int currentSize, TimeSpan time)
        {
            return currentSize * SyncIncreaseRate;
        }

        private int GetDecreasedBatchSize(int currentSize, TimeSpan time)
        {
            return GetDecreasedBatchSize(currentSize);
        }

        private int GetDecreasedBatchSize(int currentSize)
        {
            int newSize = currentSize / SyncDecreaseRate;
            return newSize > 1 ? newSize : 1;
        }

        private void CallPostSync(EntityMeta entity, SyncAction action, int batchRows)
        {
            try
            {
                PlatformProxy.PostSyncSubscriptionRequest request = new PlatformProxy.PostSyncSubscriptionRequest();
                request.SubscriptionId = new Guid(SubscriptionId);
                request.EntityName = entity.EntityName;
                request.SyncAction = action;
                request.BatchSize = batchRows;

                Service.Execute(request);
            }
            catch (SoapException e)
            {
                String message = String.Format(CultureInfo.InvariantCulture, "PostSync failed for entity '{0}', action={1}, batchRows={2} with exception {3}",
                    entity.EntityName, action.ToString(), batchRows.ToString(CultureInfo.InvariantCulture), e);
                Utility.LogTraceError(message);
                throw e;
            }
        }


        #region BcpTransferDataProvider Code

        #region Properties
        String localDirectoryName = null;
        private Guid _organizationId = Guid.Empty;

        private enum Action
        {
            Insert = 1,
            Delete = 2
        }

        private String LocalDirectoryName
        {
            get
            {
                if (localDirectoryName == null)
                {
                    localDirectoryName = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
                    localDirectoryName += "\\Microsoft\\MSCRM\\BCP";
                }

                if (!Directory.Exists(localDirectoryName))
                {
                    Directory.CreateDirectory(localDirectoryName);
                }

                return localDirectoryName;
            }
        }
        #endregion

        #region BCPTransferDataProvider Methods
        public long ExecuteDelete(String entityName, int count)
        {
            try
            {
                ValidateAll(entityName, count);

                String fileName = GetBCPFile(entityName, Action.Delete, count);
                long dataSize = 0;
                bool wasCompleted = false;

                try
                {
                    wasCompleted = true;
                }
                finally
                {
                    FileInfo bcpFile = new FileInfo(fileName);
                    dataSize = bcpFile.Length;
                    if (wasCompleted)
                        bcpFile.Delete();
                }

                return dataSize;
            }
            finally
            {
            }
        }

        public long ExecuteInsert(String entityName, int count)
        {
            try
            {
                ValidateAll(entityName, count);

                String fileName = GetBCPFile(entityName, Action.Insert, count);

                long dataSize = 0;
                bool wasCompleted = false;

                try
                {
                    wasCompleted = true;
                }
                finally
                {
                    FileInfo bcpFile = new FileInfo(fileName);
                    dataSize = bcpFile.Length;
                    if (wasCompleted)
                        bcpFile.Delete();
                }

                return dataSize;
            }
            finally
            {
            }
        }

        #endregion

        #region Shared methods for Delete and Insert
        private void ValidateAll(String entityName, int count)
        {
            if (entityName == null || entityName.Length == 0)
                throw new Exception((entityName.ToString() + "should be specified"));

            if (count < 0)
                throw new Exception("Count rows should be specified" + count.ToString());
        }

        private String GetBCPFile(String entityName, Action action, int batchSize)
        {
            String bcpFileName = LocalDirectoryName + "\\" + subscriptionId + ".bcp";
            String actionInt = ((int)action).ToString();

            String fileUrl = String.Format("{0}/OfflineSync.ashx?SubscriptionId={1}&EntityName={2}&Action={3}&BatchSize={4}", serverUrl, subscriptionId, entityName, actionInt, batchSize);

            string baseUrl = serverUrl.Remove(serverUrl.IndexOf("/MSCRMServices"));
            Uri baseUri = new Uri(baseUrl);
            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(fileUrl);
            httpWebRequest.Credentials = NetworkCredential;
            httpWebRequest.Timeout = Timeouts.GetExtendedTimeout();
            WebResponse myResponse = null;
            try
            {
                myResponse = httpWebRequest.GetResponse();
            }
            catch (WebException e)
            {
                String message = String.Format("GetBCPFile() failed during action {0} for entity '{1}', batchRows={2} with exception {3}",
                    action.ToString(), entityName,
                    batchSize.ToString(),
                    e);
                Utility.LogTraceError(message);

                throw new CrmException(message, e, ErrorCodes.GoOfflineGetBCPFileException);
            }

            Stream ReceiveStream = myResponse.GetResponseStream();

            BinaryReader readStream = new BinaryReader(ReceiveStream);

            long downloadedBytes = 0;

            try
            {
                long fileSize = 0;

                ValidateBCPFileHeader(ref readStream, out fileSize);

                downloadedBytes = DownloadBCPFile(bcpFileName, ref readStream, fileSize);
            }
            finally
            {
                readStream.Close();
                myResponse.Close();
            }

            return bcpFileName;
        }

        private void ValidateBCPFileHeader(ref BinaryReader readStream, out long fileSize)
        {
            try
            {
                int hResult = readStream.ReadInt32();
                if (hResult != 0)
                {
                    throw new CrmException(ErrorCodes.GoOfflineServerFailedGenerateBCPFile);
                }

                fileSize = readStream.ReadInt64();
            }
            catch (CrmException)
            {
                throw;
            }
            catch (Exception e)
            {
                String error = String.Format("Failed to read BCP file header. Details {1}", e.Message);
                Utility.LogTraceError(error);
                throw e;
            }
        }

        private long DownloadBCPFile(String bcpFileName, ref BinaryReader readStream, long fileSize)
        {
            FileStream fs = new FileStream(bcpFileName, FileMode.Create, FileAccess.Write, FileShare.Read);
            BinaryWriter stream = new BinaryWriter(fs);
            long totalCount = 0;

            try
            {
                const int bufferSize = 32768;
                byte[] read = new byte[bufferSize];
                int count = 0;
                while ((count = readStream.Read(read, 0, bufferSize)) > 0)
                {
                    stream.Write(read, 0, count);
                    totalCount += count;
                }

                if (totalCount != (fileSize + 4))
                {
                    String error = String.Format("BCP file size is incorrect. Actual:{0}. Expected:{1}",
                        totalCount, fileSize);
                    Utility.LogTraceError(error);

                    throw new CrmException(ErrorCodes.GoOfflineBCPFileSize);
                }
            }
            catch (CrmException)
            {
                throw;
            }
            catch (Exception e)
            {
                String error = String.Format("Failed to download BCP file. Details {1}", e.Message);
                Utility.LogTraceError(error);
                throw e;
            }
            finally
            {
                stream.Close();
                fs.Close();
            }

            return totalCount;
        }

        #endregion
        #endregion
    }

    public class SyncDataRecorder
    {
        public TimeSpan CreateSubscription;
        public TimeSpan PrepareSync;
        public TimeSpan GetSyncInfo;
        public String SyncInfo;
        public TimeSpan SchemaChanges;
        public TimeSpan MoveData;

        public XmlElement SerializeToXML(XmlDocument xmld)
        {
            XmlElement el;
            XmlAttribute att;

            el = xmld.CreateElement("Sync_Data");

            att = xmld.CreateAttribute("CreateSubscription");
            att.Value = CreateSubscription.ToString();
            el.Attributes.Append(att);

            att = xmld.CreateAttribute("PrepareSync");
            att.Value = PrepareSync.ToString();
            el.Attributes.Append(att);

            if (SyncInfo != null)
            {
                int count = ParseSyncInfo(SyncInfo);
                att = xmld.CreateAttribute("TotalEntity");
                att.Value = count.ToString();
                el.Attributes.Append(att);
                el.InnerXml = SyncInfo;
            }


            att = xmld.CreateAttribute("MoveData");
            att.Value = MoveData.ToString();
            el.Attributes.Append(att);

            return (el);
        }

        public int ParseSyncInfo(string SyncInfo)
        {
            int count = 0;
            string nl = System.Environment.NewLine;
            string[] lines = SyncInfo.Split(new char[] { '/' });

            for (int i = 0; i < lines.Length - 1; i++)
            {
                string[] equals = lines[i].Split(new char[] { '=' });

                if (equals.Length == 3)
                {
                    string countS = equals[2];
                    countS = countS.Replace("\"", "");
                    count = count + Convert.ToInt32(countS);
                }
                else
                {
                    if (equals.Length > 3)
                    {
                        string countS = equals[2];
                        countS = countS.Replace("\"", "");
                        string[] insertCountS = countS.Split(new char[] { ' ' });
                        countS = insertCountS[0];
                        Console.WriteLine("countS:" + countS);
                        count = count + Convert.ToInt32(countS);
                        countS = equals[3];
                        countS = countS.Replace("\"", "");
                        count = count + Convert.ToInt32(countS);
                    }
                }

            }
            return count;
        }
    }
}
